#pragma once
#include <iostream>
#include "Socket.hpp"
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#include "Log.hpp"
extern Log lg;

// 默认端口号
static const uint16_t defaultport = 8080;

// select 中的 fd_set 位图比特位的个数上限为 1024, 也就是说在一个事件中, 最多能同时监视 1024 个 fd
// 因此我们需要创建一个辅助数组来进行 select 函数的信息传递
static const int fd_num_max = (sizeof(fd_set)*8); // 辅助数组的最大长度
const int defaultfd = -1; // 辅助数组中元素的默认值

class SelectServer
{
public:
    SelectServer(uint16_t port = defaultport)
        : _port(port)
    {
        // 初始化辅助数组
        for (int i = 0; i < fd_num_max; i++)
        {
            fd_array[i] = defaultfd;
        }
    }

    bool Init()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();

        return true;
    }

    void PrintFd()
    {
        std::cout << "online fd list: ";
        for (int i = 0; i < fd_num_max; i++)
        {
            if (fd_array[i] == defaultfd) continue;
            std::cout << fd_array[i] << " ";
        }
        std::cout << std::endl;
    }

    void HandlerEvent(fd_set &rfds)
    {
        for (int i = 0; i < fd_num_max; i++)
        {
            int fd = fd_array[i];
            if (fd == defaultfd) continue;

            // 事件已经就绪
            if(FD_ISSET(fd, &rfds))
            {
                // 当前 fd 是监听套接字
                if (fd == _listensock.Fd())
                {
                    // 连接事件已经就绪
                    std::string clientip;
                    uint16_t clientport = 0;

                    // 会在这里阻塞吗?不会
                    int sock = _listensock.Accept(&clientip, &clientport); 
                    // 获取 sock 后不能直接 read, 因为可能不会立刻发送数据 -> 交由 select 关心
                    // 服务器接受一个新的客户端连接（sock）之后，不应该立即尝试从这个新的套接字读取数据。
                    // 原因是客户端可能不会立即发送数据，如果服务器立即尝试读取，那么读取操作可能会阻塞，因为没有任何数据可读。
                    if (sock < 0) return;
                    lg(Info, "accept success, %s:%d, sockfd: %d", clientip.c_str(), clientport, sock);

                    // "交由 select 关心"意味着服务器应该将这个新的套接字（sock）添加到 fd_set 集合中，然后继续使用 select 函数来监控这个套接字。  
                    // 找到一个最新的 pos 来放置 sock
                    int pos = 1;
                    for(; pos < fd_num_max; pos++)
                    {
                        if (fd_array[pos] != defaultfd) continue;
                        else break;
                    }
                    if (pos == fd_num_max) // 服务器已满
                    {
                        lg(Warning, "server is full, close %d now!", sock);
                        close(sock);
                    }
                    else // 将 sock 设置进 fd_array 中
                    {
                        fd_array[pos] = sock;
                        PrintFd(); // 打印正在使用的 sockfd
                    }
                } 
                else // 当前 fd 不是监听套接字
                {
                    char buffer[1024];
                    ssize_t n = read(fd, buffer, sizeof(buffer)-1);
                    if(n > 0)
                    {
                        buffer[n] = 0;
                        std::cout << "get a messge: " << buffer << std::endl;
                    }
                    else if(n == 0)
                    {
                        lg(Info, "client quit, me too, close fd %d", fd);
                        close(fd);
                        fd_array[i] = defaultfd; // 这里本质是从select中移除
                    }
                    else
                    {
                        lg(Warning, "recv error: fd is %d", fd);
                        close(fd);
                        fd_array[i] = defaultfd; // 这里本质是从select中移除
                    }
                }       
            }
        }        
    }

    void Start()
    {
        int listensock =_listensock.Fd();
        fd_array[0] = listensock;
        for (;;)
        {
            // 每次都要重新设置 rfds
            fd_set rfds;
            FD_ZERO(&rfds);

            // 使用辅助数组 fd_array 进行 select 函数信息传递
            // 设置 select 函数需要监控的文件描述符集合
            int maxfd = fd_array[0]; // select 的第一个参数
            for (int i = 0; i < fd_num_max; i++)
            {
                // fd_array 若为默认值则表明不需要关心这个 fd
                if (fd_array[i] == defaultfd)
                {
                    continue;
                }
                FD_SET(fd_array[i], &rfds);
                // 不断更新 maxfd
                if (maxfd < fd_array[i])
                {
                    maxfd = fd_array[i];
                    lg(Debug, "maxfd update, maxfd: %d", maxfd);
                    sleep(1);
                }
            }
            // 不能直接accept! accept 的功能是检测并获取 listensock 上面的事件，新连接到来，等价于读事件就绪

            // 输入输出型参数，需要周期性的重复设置，否则可能会陷入死循环
            struct timeval timeout = {0, 0};
            
            // select 的特点: 如果事件就绪，上层一直不处理，select 就会一直通知用户!
            // select 告诉用户资源就绪后，接下来的一次读取，在读取 fd 的时候，不会被阻塞
            int n = select(maxfd+1, &rfds, nullptr, nullptr, /*&timeout*/nullptr);
            switch (n)
            {
            case 0:
                lg(Info, "time out, timeout: %ld, %ld", (long)timeout.tv_sec, (long)timeout.tv_usec);
                break;
            case -1:
                lg(Error, "select error");
                break;
            default: // 有事件就绪
                lg(Info, "get a new link!");
                HandlerEvent(rfds);
                break;
            }
        }
    }

    ~SelectServer()
    {
        _listensock.Close();
    }
private:
    Sock _listensock;               // 监听套接字
    uint16_t _port;                 // 端口号
    int fd_array[fd_num_max];       // 辅助读事件的数组
    // int wfd_array[fd_num_max];   // 辅助写事件的数组
    // int expfd_array[fd_num_max]; // 辅助异常事件的数组
};
